package it.avantsoft.ls.gl;

import it.avantsoft.ls.gl.command.GroupChildCommand;
import it.avantsoft.ls.gl.command.SynchronousCommand;
import org.apache.log4j.Logger;

import java.io.DataInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.LinkedBlockingQueue;

public class ReceiverCommandFromChild extends Thread {
    private static Logger log = Logger.getLogger(ReceiverCommandFromChild.class);

    private boolean stop;

    private GroupChildHandler childHandler;
    private DataInputStream childDataInput;

    private LinkedBlockingQueue<GroupChildCommand> commandQueue;

    private static Map<String, TimerCommand> syncCommands;
    //TODO list TimerCommand
//    private Map<Byte, List<TimerCommand>> syncCommands;

    public ReceiverCommandFromChild(GroupChildHandler child, InputStream input, LinkedBlockingQueue<GroupChildCommand> commandQueue) {
        super("ReceiverCommandFromChild " + child.getChildId());

        this.childHandler = child;
        this.childDataInput = new DataInputStream(input);

        this.commandQueue = commandQueue;
        if (syncCommands == null) {
            syncCommands = new HashMap<String, TimerCommand>();
        }
//        syncCommands = new HashMap<Byte, List<TimerCommand>>();

        stop = false;
    }

    public void run() {
        while (!stop) {
            enqueue(readCommandFromInputStream());
        }
    }

    /**
     * Waits for the commandQueue's lock then adds the new generated command and notifies
     *
     * @param peerCommand The new command to be added
     */
    private void enqueue(GroupChildCommand peerCommand) {
        if (peerCommand != null) {
            try {
                commandQueue.put(peerCommand);
            } catch (InterruptedException e) {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            }
        }
    }

    //////////////////////////////////////////////// Sync Command

    private final Timer timer = new Timer("TimerCommands", true);

    private class TimerCommand extends TimerTask {
        private SynchronousCommand command;

        public TimerCommand(SynchronousCommand command) {
            this.command = command;
        }

        public void run() {
            log.info("execute SynchronousCommand " + command);
            command.execute();
        }
    }

    public TimerCommand scheduleTimerCommand(String childId, SynchronousCommand synchronousCommand) {
        TimerCommand timerCommand = new TimerCommand(synchronousCommand);
        timer.schedule(timerCommand, synchronousCommand.getTimeout());

        syncCommands.put(childId + '_' + synchronousCommand.getCommandByte(), timerCommand);

//        List<TimerCommand> list = syncCommands.get(synchronousCommand.getCommandByte());
//        if (list == null) {
//            list = new ArrayList<TimerCommand>();
//        }
//        list.add(timerCommand);
        return timerCommand;
    }

    ///////////////////////////////////////////////

    /**
     * Reads and parse messages from input stream. It creates an ObjectOutputStream on the InputStream and reads objectsw
     *
     * @return A PeerCommand
     */
    private GroupChildCommand readCommandFromInputStream() {
        GroupChildCommand command = null;
        try {
            byte commandByte = childDataInput.readByte();
            boolean discard = false;
            String key = childHandler.getChildId() + '_' + commandByte;
            TimerCommand timerCommand = null;
            if (syncCommands.containsKey(key)) {
                timerCommand = syncCommands.remove(key);
                boolean cancelled = timerCommand.cancel();
                log.info("cancel " + timerCommand.command + ": " + cancelled);
                if (!cancelled) { //perchè è stato gia eseguito
                    discard = true;
                    log.info("not cancelled: discard message");
                }
            }

            command = GroupChildCommandFactory.getCommand(childHandler, childDataInput, commandByte);

            if (timerCommand != null && discard) {
                //Non considero i comandi che arrivano in ritardo
                command = null;
            }
        } catch (EOFException e) {
            stop = true;

            checkDeath();
        } catch (IOException e) {
            stop = true;
        }

        return command;
    }

    private void checkDeath() {
        String childId = childHandler.getChildId();
        if (!childHandler.isConnected() && childHandler.peerKnown(childId)) {
            log.info("stopped: morte del child " + childId);
            childHandler.signalDeadSonEvent(childId);
        } else {
            log.info("stopped: child sconosciuto " + childId);
        }
    }
}
